package online.lona.mc.bukkit.plugin.ymer;

import online.lona.mc.LibraryUtil;
import online.lona.mc.NativeInterface;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;

import java.lang.reflect.Field;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.io.*;
import java.util.concurrent.Callable;

public class Ymer extends JavaPlugin implements Listener {
    private Map<String, StandardYmerPlayer> ymerPlayerMap = new HashMap<>();
    private Map<String, Boolean> playerModCheckStatusMap = new HashMap<>();
    private List<String> alreadyLoginPlayerList = new ArrayList();
    public static final String PLUGIN_ROOT = "plugins/ymer";
    public static Plugin plugin = null;
    public static Ymer Ymer = null;
    public int anti_cheat_kick_count = 0;
    public int timeout_kick_count = 0;
    public Map<String, ModCheckTask> taskMap = new HashMap<>();

    @Override
    public void onEnable() {
        plugin = this;
        Ymer = this;
        enable();
    }

    private void enable() {
        Config.init();
        initMetrics();
        getServer().getLogger().info("Ymer 启动");
        try {
            final String libraryPath = System.getProperty("user.dir") + "/" + PLUGIN_ROOT;
            LibraryUtil.releaseLibrary(libraryPath);
            LibraryUtil.addLibraryDir(libraryPath);
        } catch (Exception e) {
            getServer().getLogger().warning("Ymer 启动失败");
            getServer().getLogger().warning("无法添加库路径");
            getServer().getLogger().warning("Ymer将关闭服务器");
            getServer().getPluginManager().disablePlugin(this);
            getServer().shutdown();
            return;
        }
        if (!isCoreFileExisit()) {
            getServer().getLogger().warning("Ymer 启动失败");
            getServer().getLogger().warning("核心配置文件错误");
            getServer().getLogger().warning("请确认库文件与特征文件已正确安置");
            getServer().getLogger().warning("Ymer将关闭服务器");
            getServer().getPluginManager().disablePlugin(this);
            getServer().shutdown();
            return;
        }
        try {
            NativeInterface.serverInit();
        } catch (Exception ignored) {
            getServer().getLogger().warning("Ymer 启动失败");
            getServer().getLogger().warning("无法载入yemr库");
            getServer().getLogger().warning("Ymer将关闭服务器");
            getServer().getPluginManager().disablePlugin(this);
            getServer().shutdown();
            return;
        }

        this.getServer().getPluginManager().registerEvents(this, this);
        this.getServer().getMessenger().registerIncomingPluginChannel(this, NativeInterface.CHANNEL_NAME, new PluginMessageListener() {
            @Override
            public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
                final String playerName = player.getName();
                if (player.isValid() && player.isOnline()) {
                    if (ymerPlayerMap.containsKey(playerName)) {
                        NativeInterface.getInstance().onClientMessage(bytes, ymerPlayerMap.get(playerName));
                    }
                }
            }
        });
        this.getServer().getMessenger().registerOutgoingPluginChannel(this, NativeInterface.CHANNEL_NAME);
    }

    @EventHandler
    public void onPlayerJoinGame(final PlayerJoinEvent event) {
        final Player player = event.getPlayer();
        final String playerName = player.getName();
        final StandardYmerPlayer ymerPlayer = new StandardYmerPlayer(player);
        ymerPlayerMap.put(playerName, ymerPlayer);
        playerModCheckStatusMap.put(playerName, false);
        getServer().getScheduler().runTaskLater(this, new Runnable() {
            @Override
            public void run() {
                if (player.isOnline()) {
                    NativeInterface.getInstance().onPlayerJoinGame(ymerPlayer);
                    ModCheckTask modCheckTask = new ModCheckTask(playerName, ymerPlayer);
                    taskMap.put(playerName, modCheckTask);
                    getServer().getScheduler().runTaskLater(Ymer.plugin, modCheckTask, (Config.getInstance().getAuthTimeout() / 8) * 20);
                }
            }
        }, (Config.getInstance().getAuthTimeout() / 3) * 20);
        for (final String name : alreadyLoginPlayerList) {
            if (name.equals(playerName)) {
                if (player.isOnline()) {
                    player.kickPlayer(Config.getInstance().getAvatarKickMessage());
                }
                return;
            }
        }
        alreadyLoginPlayerList.add(playerName);
    }

    private void onPlayerExit(final Player player) {
        final String playerName = player.getName();
        //不能随意修改顺序 start
        NativeInterface.getInstance().onPlayerQuit(ymerPlayerMap.get(playerName));
        ymerPlayerMap.remove(playerName);
        playerModCheckStatusMap.remove(playerName);
        //不能随意修改顺序 end
        taskMap.get(playerName).setCancelled(true);
        int pos = 0;
        for (final String name : alreadyLoginPlayerList) {
            if (name.equals(playerName)) {
                alreadyLoginPlayerList.remove(pos);
                break;
            }
            pos++;
        }
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        onPlayerExit(event.getPlayer());
    }


//    @EventHandler
//    public void onPlayerKicked(PlayerKickEvent event) {
//        onPlayerExit(event.getPlayer());
//    }

    @Override
    public void onDisable() {

    }

    private boolean isCoreFileExisit() {
        File file = new File(PLUGIN_ROOT);
        if (!file.exists() || !file.isDirectory()) {
            file.mkdirs();
            return false;
        }
        file = new File(PLUGIN_ROOT + "/rsa_public.key");
        if (file.exists()) {
            file.delete();
        }
        file = new File(PLUGIN_ROOT + "/rsa_private.key");
        if (file.exists()) {
            file.delete();
        }
        file = new File(PLUGIN_ROOT + "/SHA512.txt");
        if (!file.exists()) {
            return false;
        }
        try {
            generateRSAKeyPair();
        } catch (IOException | NoSuchAlgorithmException e) {
            return false;
        }
        return true;
    }

    private static void generateRSAKeyPair() throws IOException, NoSuchAlgorithmException {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(2048);
            KeyPair pair = generator.genKeyPair();
            PublicKey publicKey = pair.getPublic();
            PrivateKey privateKey = pair.getPrivate();
            PKCS8EncodedKeySpec publicKeySpec = new PKCS8EncodedKeySpec(publicKey.getEncoded());
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
            File privateKeyFile = new File(PLUGIN_ROOT + "/rsa_private.key");
            File publicKeyFile = new File(PLUGIN_ROOT + "/rsa_public.key");
            writeStringToFile(publicKeyFile, "-----BEGIN PUBLIC KEY-----\n", true);
            writeStringToFile(publicKeyFile, Base64.getMimeEncoder().encodeToString(publicKeySpec.getEncoded()), true);
            writeStringToFile(publicKeyFile, "\n-----END PUBLIC KEY-----", true);
            writeStringToFile(privateKeyFile, "-----BEGIN PRIVATE KEY-----\n", true);
            writeStringToFile(privateKeyFile, Base64.getMimeEncoder().encodeToString(privateKeySpec.getEncoded()), true);
            writeStringToFile(privateKeyFile, "\n-----END PRIVATE KEY-----", true);
        } catch (Exception e) {
            throw e;
        }
    }


    private void initMetrics() {
        Metrics metrics = new Metrics(this);
        metrics.addCustomChart(new Metrics.SingleLineChart("anti_cheat_kick", new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return anti_cheat_kick_count;
            }
        }));
        metrics.addCustomChart(new Metrics.SingleLineChart("timeout_kick", new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return timeout_kick_count;
            }
        }));
    }

    public static void writeStringToFile(File file, String str, boolean isAppend) throws IOException {
        FileWriter writer = new FileWriter(file, isAppend);
        writer.write(str);
        writer.close();
    }

    public boolean getPlayerCheckStatus(final String playerName) {
        return playerModCheckStatusMap.getOrDefault(playerName, false);
    }

    public void setPlayerCheckStatus(final String playerName, final boolean status) {
        playerModCheckStatusMap.put(playerName, status);
    }
}
